import os
import shutil
import glob
import filecmp
import subprocess
import sys

#tip: Qt等开源库版本在x86_64和arm64下必须保持完全一致

def recursive_merge_binaries(src0, src1, dst):
    for newpath0 in glob.glob(src0+"/*"):
        filename = os.path.basename(newpath0)
        newpath1 = os.path.join(src1, filename)
        if not os.path.exists(newpath1):
            continue
        if os.path.islink(newpath0) and os.path.islink(newpath1):
            if os.path.relpath(newpath0, src0) == os.path.relpath(newpath1, src1):
                continue
        if os.path.isdir(newpath0) and os.path.isdir(newpath1):
            continue
        if (not os.path.islink(newpath0)) and (not os.path.islink(newpath1)):
            if os.path.isfile(newpath0) and os.path.isfile(newpath1):
                continue
        raise Exception(f"{newpath0} and {newpath1} cannot be " +
                        "merged into a universal binary because they are of " +
                        "incompatible types. Perhaps the installed libraries" +
                        " are from different versions for each architecture")
    for newpath0 in glob.glob(src0+"/*"):
        filename = os.path.basename(newpath0)
        newpath1 = os.path.join(src1, filename)
        new_dst_path = os.path.join(dst, filename)
        if os.path.islink(newpath0):
            continue
        if not os.path.exists(newpath1):
            if os.path.isdir(newpath0):
                shutil.copytree(newpath0, new_dst_path,symlinks=True)
            else:
                shutil.copy(newpath0, new_dst_path)
            continue
        if os.path.isdir(newpath1):
            os.mkdir(new_dst_path)
            recursive_merge_binaries(newpath0, newpath1, new_dst_path)
            continue
        if filecmp.cmp(newpath0, newpath1):
            shutil.copy(newpath0, new_dst_path)
        else:
            file_output = subprocess.check_output(["file", newpath0], text=True)
            if "Mach-O" in file_output:
                lipo(newpath0, newpath1, new_dst_path)
            else:
                print(f"Skipping non-binary file: {newpath0}")
    for newpath1 in glob.glob(src1+"/*"):
        filename = os.path.basename(newpath1)
        newpath0 = os.path.join(src0, filename)
        new_dst_path = os.path.join(dst, filename)
        if (not os.path.exists(newpath0)) and (not os.path.islink(newpath1)):
            if os.path.isdir(newpath1):
                shutil.copytree(newpath1, new_dst_path,symlinks=True)
            else:
                shutil.copy(newpath1, new_dst_path)
    for newpath0 in glob.glob(src0+"/*"):
        filename = os.path.basename(newpath0)
        new_dst_path = os.path.join(dst, filename)
        if os.path.islink(newpath0):
            relative_path = os.path.relpath(os.path.realpath(newpath0), src0)
            os.symlink(relative_path, new_dst_path)
    for newpath1 in glob.glob(src1+"/*"):
        filename = os.path.basename(newpath1)
        new_dst_path = os.path.join(dst, filename)
        newpath0 = os.path.join(src0, filename)
        if os.path.islink(newpath1) and not os.path.exists(newpath0):
            relative_path = os.path.relpath(os.path.realpath(newpath1), src1)
            os.symlink(relative_path, new_dst_path)

def lipo(path0, path1, dst):
    if subprocess.call(["lipo", "-create", "-output", dst, path0, path1]) != 0:
        print(f"WARNING: {path0} and {path1} cannot be lipo'd")
        shutil.copy(path0, dst)

def lipo_exe(app_path):
    original_file = app_path+"/Contents/MacOS/qeubee_arm64"
    other_file = app_path+"/Contents/MacOS/qeubee_x86_64"
    output_file = app_path+"/Contents/MacOS/qeubee"
    try:
        subprocess.check_call([
            "lipo",
            "-create",
            original_file,
            other_file,
            "-output",
            output_file
        ])
        print(f"Universal binary created at {output_file}")
    except subprocess.CalledProcessError as e:
        print(f"Error in lipo command: {e}")
    try:
        os.remove(original_file)
        print(f"Deleted original file: {original_file}")
    except OSError as e:
        print(f"Error deleting {original_file}: {e}")
    try:
        os.remove(other_file)
        print(f"Deleted other file: {other_file}")
    except OSError as e:
        print(f"Error deleting {other_file}: {e}")
    

def sign(app_path):
    try:
        subprocess.run([
            "codesign",
            "--deep",
            "--force",
            "--verbose",
            "--sign", "Mac Developer: 沈 浩杰 (7J2LY5RWZ3)",
            app_path
        ],check=True,
        stdout=sys.stdout,
        stderr=sys.stderr)
        print(f"Successfully signed {app_path}")
    except subprocess.CalledProcessError as e:
        print(f"Error in codesigning: {e}")

    dmg_path = os.path.splitext(app_path)[0] + ".dmg"
    if os.path.exists(dmg_path):
        try:
            os.remove(dmg_path)
            print(f"Removed existing {dmg_path}")
        except OSError as e:
            print(f"Error removing {dmg_path}: {e}")
    try:
        subprocess.check_call([
            "create-dmg",
            dmg_path,
            app_path
        ])
        print(f"DMG created: {dmg_path}")
    except subprocess.CalledProcessError as e:
        print(f"Error creating DMG: {e}")


if __name__ == "__main__":
    dst_app = "qeubee.app"

    if os.path.exists(dst_app):
        shutil.rmtree(dst_app)

    os.mkdir(dst_app)

    src_app0 = "qeubee_arm64.app/"
    src_app1 = "qeubee_x86_64.app/"

    recursive_merge_binaries(src_app0, src_app1, dst_app)
    lipo_exe(dst_app)
    sign(dst_app)